#!/bin/sh
#************> Script to Attach MCP Distribution ID To Executable <*************
#************> ================================================== <*************

# Usage:
#
#	tag_it distro_id exe_filename
#	distro_id exe_filename
#	untag_it exe_filename
#
#	log_update update_distro_id update_pkg_name exe_filename
#	update_log exe_filename
#	rm_update_log exe_filename

# Revision ID: $Id$

#*******************************************************************************
#*******************************************************************************

#set -x

# ID_SECTION_NAME - Name of section we're holding the distro ID in the
#	ELF file.
# UPDATE_SECTION_NAME - Name of section we're holding the update log in the
#	ELF file.
# TMP_EXE_FILE - Name of temporary file for the body of the new file section.
# TMP_TAG_FILE - Name of copy of ELF file that we tweak to extract distro_id.
# TMP_TAG_FILE2 - Name of copy of ELF file that we tweak to extract an ELF
#	file section.
# LINE_FMT_VERSION - Format being used for update log line.
# SCRATCH_DIR - Where we keep the herd of tmp files that we create.

ID_SECTION_NAME=".distro_id_tag"
UPDATE_SECTION_NAME=".update_log"
SCRATCH_DIR="${TMP_DIR:-/tmp}/distro_id$$"
TMP_EXE_FILE="${SCRATCH_DIR}/distro_id"
TMP_TAG_FILE="${SCRATCH_DIR}/file1"
TMP_TAG_FILE2="${SCRATCH_DIR}/file2"
LINE_FMT_VERSION=1
OBJ_MSG="${SCRATCH_DIR}/obj_msg"

TRUE=0
FALSE=1





##
## Usage Message for tag_it(1).
##

TagItUsage () {
	{
	echo "Usage:"
	echo "	tag_it distro_id exe_filename"
	} >&2
}



##
## Usage Message for log_update(1).
##

LogUpdateUsage () {
	{
	echo "Usage:"
	echo "	log_update update_distro_id update_package_name exe_filename"
	} >&2
}



##
## Usage Message for untag_it(1).
##

UntagItUsage () {
	{
	echo "Usage:"
	echo "	untag_it exe_filename"
	} >&2
}



##
## Usage Message for distro_id(1).
##

DistroIdUsage () {
	{
	echo "Usage:"
	echo "	distro_id exe_filename"
	} >&2
}



##
## Usage Message for update_log(1).
##

UpdateLogUsage () {
	{
	echo "Usage:"
	echo "	update_log exe_filename"
	} >&2
}



##
## Usage Message for update_log(1).
##

RmUpdateLogUsage () {
	{
	echo "Usage:"
	echo "	rm_update_log exe_filename"
	} >&2
}


##
## Determine If Distro ID Is Present
##

# Returns true (zero) if ELF section is present, false (!0) otherwise.
# When section is present objdump will have at least one line matching the name
# of the section. Grep's return code tells us this (0 = match found).

HasSection () {
	# $1 is name of file to check.
	# $2 is name of section to look for.

	Rtn=`$LCL_OBJDUMP -j $2 -s $1 | grep $2 > /dev/null 2>&1; echo $?`
	return $Rtn
}

##
## Is File ELF Binary
##

# Returns true (zero) if file is recognized ELF file, false (!0) otherwise.

IsElfBinary () {
	# $1 is name of file to check.

	Rtn=`$LCL_OBJDUMP -a $1 > /dev/null 2>&1; echo $?`
	return $Rtn
}



##
## Echo ELF Section Routine
##

GetSection () {
	# $1 is name of file to check.
	# $2 is name of section to look for.

	# Save off passed arguments.

	File="$1"
	Section="$2"

	# Make sure the file exists.

	if [ ! -f "$File" ]; then
		echo "$Prog: no such file ($File)" >&2
		exit $FALSE
	fi

	# Make sure this is a binary file.

	if ! IsElfBinary $File; then
		echo "$Prog: Unrecognized format of '$File'. Not ELF binary." >&2
		exit $FALSE
	fi

	# Make sure this file does have a distro_id section.

	if ! HasSection $File $Section; then
		if [ "$2" = "$ID_SECTION_NAME" ]; then
			echo "$Prog: '$File' does not have a distribution ID." >&2
		else
			echo "$Prog: '$File' does not have an update log." >&2
		fi
		exit $FALSE
	fi

	# This is a little trick we do to extract the info in the section we're
	# after. If you don't do this, I don't know how to persuade objcopy
	# to cough up the section we're after.

	cp $File $TMP_EXE_FILE
	$LCL_OBJCOPY --set-section-flags $Section=alloc,data \
	  $TMP_EXE_FILE > /dev/null 2>&1
	$LCL_OBJCOPY -j $Section \
	  --output-target=binary $TMP_EXE_FILE $TMP_TAG_FILE2

	cat $TMP_TAG_FILE2

	rm -f $TMP_EXE_FILE

} #GetSection


##
## Remove Section Routine
##

RemoveSection () {
	# $1 is name of file to check.
	# $2 is name of section to look for.

	# Save off command line arguments in a handy form.

	File="$1"
	Section="$2"

	# Make sure the file exists.

	if [ ! -f "$File" ]; then
		echo "$Prog: no such file ($File)" >&2
		exit $FALSE
	fi

	# Make sure this is a binary file.

	if ! IsElfBinary $File; then
		echo "$Prog: Unrecognized format of '$File'. Not ELF binary." >&2
		exit $FALSE
	fi

	# Make sure this file does not have a distro_id section.

	if ! HasSection $File $Section; then
		echo "$Prog: '$File' does not have a distribution ID to remove." >&2
		return $TRUE
	fi

	# Add the distro_id section.

	$LCL_OBJCOPY --remove-section=$Section $File
	RS_Rtn=$?

	return $RS_Rtn

} #RemoveSection


##
## Cleanup routine
##

CleanUp () {
	rm -fr $SCRATCH_DIR
}


##
## Main
##

# Extract the program name from the command line.

Prog=`expr /$0 : '.*/\(.*\)'`
trap "CleanUp; exit 2" 1 2 3 15

LCL_OBJCOPY=${OBJCOPY:-objcopy}
LCL_OBJDUMP=${OBJDUMP:-objdump}

# This script is really several programs in one. Which version is the user
# trying to invoke (get from name of command).

if ! mkdir -p $SCRATCH_DIR; then
	echo "$Prog: Failed to make scratch directory ($SCRATCH_DIR)." >&2
	exit 1
fi

# This script is really two programs in one. Which version is the user
# trying to invoke (get from name of command).

ExitStatus=$TRUE
if [ "$Prog" = "tag_it" ]; then

	# Make sure we have exactly 2 arguments.

	if [ $# -ne 2 ]; then
		echo "$Prog: wrong number of command line arguments." >&2
		TagItUsage
		CleanUp
		exit $FALSE
	fi

	# Save off command line arguments in a handy form. Each line could have
	# a different format (as this program changes over the years) so record
	# the format of the line.

	echo "$1" >> $TMP_TAG_FILE
	File="$2"

	# Make sure the file exists.

	if [ ! -f "$File" ]; then
		echo "$Prog: no such file ($File)" >&2
		CleanUp
		exit $FALSE
	fi

	# Make sure this is a binary file.

	if ! IsElfBinary $File; then
		echo "$Prog: Unrecognized format of ($File). Not ELF binary." >&2
		CleanUp
		exit $FALSE
	fi

	# Warning: Make sure this file has a distro_id section.

	if HasSection $File $ID_SECTION_NAME; then
		echo "$Prog: ($File) already has a distribution ID." >&2
		CleanUp
		exit $FALSE
	fi

	# Add the distro_id section.

	if ! $LCL_OBJCOPY --add-section $ID_SECTION_NAME=$TMP_TAG_FILE \
	  $File > $OBJ_MSG 2>&1; then
		echo "$Prog: failed to add distribution ID section to ($File)." >&2
		cat $OBJ_MSG
		ExitStatus=$FALSE
	fi

elif [ "$Prog" = "untag_it" ]; then

	# Make sure we have exactly 1 argument.

	if [ $# -ne 1 ]; then
		echo "$Prog: wrong number of command line arguments." >&2
		UntagItUsage
		CleanUp
		exit $FALSE
	fi

	RemoveSection $1 $ID_SECTION_NAME

elif [ "$Prog" = "distro_id" ]; then

	# Make sure we have only one argument.

	if [ $# -ne 1 ]; then
		echo "$Prog: wrong number of command line arguments." >&2
		DistroIdUsage
		CleanUp
		exit $FALSE
	fi

	GetSection $1 $ID_SECTION_NAME

elif [ "$Prog" = "update_log" ]; then

	# Make sure we have only one argument.

	if [ $# -ne 1 ]; then
		echo "$Prog: wrong number of command line arguments." >&2
		UpdateLogUsage
		CleanUp
		exit $FALSE
	fi

	GetSection $1 $UPDATE_SECTION_NAME

elif [ "$Prog" = "log_update" ]; then
	# $1 is distro_id for the update package.
	# $2 is name of update package.
	# $3 is name of file to log update in.

	# Make sure we have three arguments.

	if [ $# -ne 3 ]; then
		echo "$Prog: wrong number of command line arguments." >&2
		LogUpdateUsage
		CleanUp
		exit $FALSE
	fi

	# In case something goes wrong, we leave the old update in the ELF file
	# until the new update log is installed. That means we also need to
	# recover from an aborted update. This is done by renaming the old
	# update section.

	if HasSection $3 $UPDATE_SECTION_NAME; then
		if HasSection $3 $UPDATE_SECTION_NAME.tmp; then
			# We have a current and a temp update section. Must have been
			# cut off before we could remove the tmp update section.

			if ! RemoveSection $3 $UPDATE_SECTION_NAME.tmp; then
				echo "$Prog: Removal of update log temporary section failed, when it shouldn't." >&2
				ExitStatus=$FALSE
			fi
		fi
	else
		if HasSection $3 $UPDATE_SECTION_NAME.tmp; then
			# We have a current and a temp update section. Must have been
			# cut off before we could rename it to the update section name.

			if ! $LCL_OBJCOPY --rename-section \
			  $UPDATE_SECTION_NAME.tmp=$UPDATE_SECTION_NAME \
			  $3 > $OBJ_MSG 2>&1; then
				echo "$Prog: Rename of update log temporary section failed, when it shouldn't." >&2
				cat $OBJ_MSG
				ExitStatus=$FALSE
			fi
		fi
	fi

	# If we get this far then the file should be ok.

	# It's easy to switch the distro ID and the package name. Check that the
	# distro ID looks like a bunch of colon separated fields.

	Match="`expr \"$1\" : '[0-9]:.*:.*:'`" #0 means no match
	if [ "$Match" -eq 0 ]; then
		LogUpdateUsage
		CleanUp
		exit $FALSE
	fi

	# Create the new section

	rm -f $TMP_TAG_FILE
	if HasSection $3 $UPDATE_SECTION_NAME; then
		GetSection $3 $UPDATE_SECTION_NAME > $TMP_TAG_FILE
	fi
	echo "/$LINE_FMT_VERSION/ $1	$2	updated on `date -u` by `whoami`" \
	  >> $TMP_TAG_FILE

	if ! $LCL_OBJCOPY --add-section $UPDATE_SECTION_NAME.tmp=$TMP_TAG_FILE \
	  $3 > $OBJ_MSG 2>&1; then
		echo "$Prog: Add section step in creating update log for ($3) failed." >&2
		cat $OBJ_MSG
		ExitStatus=$FALSE
	fi
	if HasSection $3 $UPDATE_SECTION_NAME; then
		if ! RemoveSection $3 $UPDATE_SECTION_NAME; then
			echo "$Prog: Removal of update log section failed, when it shouldn't." >&2
			ExitStatus=$FALSE
		fi
	fi
	if ! $LCL_OBJCOPY --rename-section \
	  $UPDATE_SECTION_NAME.tmp=$UPDATE_SECTION_NAME $3 > $OBJ_MSG 2>&1; then
		echo "$Prog: Rename step in creating update log for ($3) failed." >&2
		cat $OBJ_MSG
		ExitStatus=$FALSE
	fi

	rm -f $TMP_TAG_FILE

elif [ "$Prog" = "rm_update_log" ]; then

	# Make sure we have exactly 1 argument.

	if [ $# -ne 1 ]; then
		echo "$Prog: wrong number of command line arguments." >&2
		RmUpdateLogUsage
		CleanUp
		exit $FALSE
	fi

	if ! RemoveSection $1 $UPDATE_SECTION_NAME; then
		echo "$Prog: Failed to remove distribution ID from ($1)." >&2
		ExitStatus=$FALSE
	fi
	if HasSection $1 $UPDATE_SECTION_NAME.tmp; then
		if ! RemoveSection $1 $UPDATE_SECTION_NAME.tmp; then
			echo "$Prog: Failed to remove temporary distribution ID from ($1)." >&2
			ExitStatus=$FALSE
		fi
	fi
fi

CleanUp

exit $ExitStatus
